/*
*
* NMEA library
* URL: http://nmea.sourceforge.net
* Author: Tim (xtimor@gmail.com)
* Licence: http://www.gnu.org/licenses/lgpl.html
* $Id: parser.c 17 2008-03-11 11:56:11Z xtimor $
*
*/

/**
* \file parser.h
*/

#include "nmea/tok.h"
#include "nmea/parse.h"
#include "nmea/parser.h"
#include "nmea/context.h"

#include <string.h>
#include <stdlib.h>

typedef struct _nmeaParserNODE
{
	int packType;
	void *pack;
	struct _nmeaParserNODE *next_node;

} nmeaParserNODE;

/*
* high level
*/

/**
* \brief Initialization of parser object
* @return true (1) - success or false (0) - fail
*/
int nmea_parser_init(nmeaPARSER *parser)
{
	int resv = 0;
	int buff_size = nmea_property()->parse_buff_size;

	NMEA_ASSERT(parser);

	if(buff_size < NMEA_MIN_PARSEBUFF)
		buff_size = NMEA_MIN_PARSEBUFF;

	memset(parser, 0, sizeof(nmeaPARSER));

	if(0 == (parser->buffer = malloc(buff_size)))
		nmea_error("Insufficient memory!");
	else
	{
		parser->buff_size = buff_size;
		resv = 1;
	}    

	return resv;
}

/**
* \brief Destroy parser object
*/
void nmea_parser_destroy(nmeaPARSER *parser)
{
	NMEA_ASSERT(parser && parser->buffer);
	free(parser->buffer);
	nmea_parser_queue_clear(parser);
	memset(parser, 0, sizeof(nmeaPARSER));
}

/**
* \brief Analysis of buffer and put results to information structure
* @return Number of packets wos parsed
*/
int nmea_parse(    
	       nmeaPARSER *parser,
	       const char *buff, int buff_sz,
	       nmeaINFO *info
	       )
{
	int ptype, nread = 0;
	void *pack = 0;

	NMEA_ASSERT(parser && parser->buffer);

	nmea_parser_push(parser, buff, buff_sz);

	while(GPNON != (ptype = nmea_parser_pop(parser, &pack)))
	{
		nread++;

		switch(ptype)
		{
		case GPGGA:
			nmea_GPGGA2info((nmeaGPGGA *)pack, info);
			break;
		case GPGSA:
			nmea_GPGSA2info((nmeaGPGSA *)pack, info);
			break;
		case GPGSV:
			nmea_GPGSV2info((nmeaGPGSV *)pack, info);
			break;
		case GPRMC:
			nmea_GPRMC2info((nmeaGPRMC *)pack, info);
			break;
		case GPVTG:
			nmea_GPVTG2info((nmeaGPVTG *)pack, info);
			break;
		case SDDBT:
			nmea_SDDBT2info((nmeaSDDBT *)pack, info);
				break;
		};

		free(pack);
	}

	return nread;
}


#define NEMA_PARSE_CASE(x) case x: \
	if(0 == (node->pack = malloc(sizeof(nmea##x)))) \
	goto mem_fail; \
	node->packType = x; \
	if(!nmea_parse_##x((const char *)parser->buffer + nparsed, sen_sz, (nmea##x *)node->pack)) \
{ free(node); node = 0; } break;
/*
* low level
*/

int nmea_parser_real_push(nmeaPARSER *parser, const char *buff, int buff_sz)
{
	int nparsed = 0, crc, sen_sz, ptype;
	nmeaParserNODE *node = 0;

	NMEA_ASSERT(parser && parser->buffer);

	/* clear unuse buffer (for debug) */
	/*
	memset(
	parser->buffer + parser->buff_use, 0,
	parser->buff_size - parser->buff_use
	);
	*/

	/* add */
	if(parser->buff_use + buff_sz >= parser->buff_size)
		nmea_parser_buff_clear(parser);

	memcpy(parser->buffer + parser->buff_use, buff, buff_sz);
	parser->buff_use += buff_sz;

	/* parse */
	for(;;node = 0)
	{
		sen_sz = nmea_find_tail(
			(const char *)parser->buffer + nparsed,
			(int)parser->buff_use - nparsed, &crc);

		if(!sen_sz)
		{
			if(nparsed)
				memcpy(
				parser->buffer,
				parser->buffer + nparsed,
				parser->buff_use -= nparsed);
			break;
		}
		else if(crc >= 0)
		{
			ptype = nmea_pack_type(
				(const char *)parser->buffer + nparsed + 1,
				parser->buff_use - nparsed - 1);

			if(0 == (node = malloc(sizeof(nmeaParserNODE))))
				goto mem_fail;

			node->pack = 0;


			switch(ptype)
			{
			case GPGGA: 
				if(0 == (node->pack = malloc(sizeof(nmeaGPGGA))))
					goto mem_fail; 
				node->packType = GPGGA; 
				if(!nmea_parse_GPGGA(
					(const char *)parser->buffer + nparsed, 
					sen_sz, 
					(nmeaGPGGA *)node->pack))
				{
					free(node); 
					node = 0; 
				} 
				break;
// 				NEMA_PARSE_CASE(GPGSA);
// 				NEMA_PARSE_CASE(GPGSV);
// 				NEMA_PARSE_CASE(GPRMC);
// 				NEMA_PARSE_CASE(GPVTG);
//  				NEMA_PARSE_CASE(GPAAM);
// 				NEMA_PARSE_CASE(GPBOD);
// 				NEMA_PARSE_CASE(GPBWW);
// 				NEMA_PARSE_CASE(GPGLL);
// 				NEMA_PARSE_CASE(GPMSK);
// 				NEMA_PARSE_CASE(PGRME);

				NEMA_PARSE_CASE(SDDBK);
				NEMA_PARSE_CASE(SDDBS);
 				NEMA_PARSE_CASE(SDDBT);
// 				NEMA_PARSE_CASE(SDDPT);
// 				NEMA_PARSE_CASE(SDMTW);
// 				NEMA_PARSE_CASE(WIMWV);
// 				NEMA_PARSE_CASE(PGRMM);
// 				NEMA_PARSE_CASE(PGRMZ);
			default:
				free(node);
				node = 0;
				break;
			};

			if(node)
			{
				if(parser->end_node)
					((nmeaParserNODE *)parser->end_node)->next_node = node;
				parser->end_node = node;
				if(!parser->top_node)
					parser->top_node = node;
				node->next_node = 0;
			}
		}

		nparsed += sen_sz;
	}

	return nparsed;

mem_fail:
	if(node)
		free(node);

	nmea_error("Insufficient memory!");

	return -1;
}

/**
* \brief Analysis of buffer and keep results into parser
* @return Number of bytes wos parsed from buffer
*/
int nmea_parser_push(nmeaPARSER *parser, const char *buff, int buff_sz)
{
	int nparse, nparsed = 0;

	do
	{
		if(buff_sz > parser->buff_size)
			nparse = parser->buff_size;
		else
			nparse = buff_sz;

		nparsed += nmea_parser_real_push(
			parser, buff, nparse);

		buff_sz -= nparse;

	} while(buff_sz);

	return nparsed;
}

/**
* \brief Get type of top packet keeped into parser
* @return Type of packet
* @see nmeaPACKTYPE
*/
int nmea_parser_top(nmeaPARSER *parser)
{
	int retval = GPNON;
	nmeaParserNODE *node = (nmeaParserNODE *)parser->top_node;

	NMEA_ASSERT(parser && parser->buffer);

	if(node)
		retval = node->packType;

	return retval;
}

/**
* \brief Withdraw top packet from parser
* @return Received packet type
* @see nmeaPACKTYPE
*/
int nmea_parser_pop(nmeaPARSER *parser, void **pack_ptr)
{
	int retval = GPNON;
	nmeaParserNODE *node = (nmeaParserNODE *)parser->top_node;

	NMEA_ASSERT(parser && parser->buffer);

	if(node)
	{
		*pack_ptr = node->pack;
		retval = node->packType;
		parser->top_node = node->next_node;
		if(!parser->top_node)
			parser->end_node = 0;
		free(node);
	}

	return retval;
}

/**
* \brief Get top packet from parser without withdraw
* @return Received packet type
* @see nmeaPACKTYPE
*/
int nmea_parser_peek(nmeaPARSER *parser, void **pack_ptr)
{
	int retval = GPNON;
	nmeaParserNODE *node = (nmeaParserNODE *)parser->top_node;

	NMEA_ASSERT(parser && parser->buffer);

	if(node)
	{
		*pack_ptr = node->pack;
		retval = node->packType;
	}

	return retval;
}

/**
* \brief Delete top packet from parser
* @return Deleted packet type
* @see nmeaPACKTYPE
*/
int nmea_parser_drop(nmeaPARSER *parser)
{
	int retval = GPNON;
	nmeaParserNODE *node = (nmeaParserNODE *)parser->top_node;

	NMEA_ASSERT(parser && parser->buffer);

	if(node)
	{
		if(node->pack)
			free(node->pack);
		retval = node->packType;
		parser->top_node = node->next_node;
		if(!parser->top_node)
			parser->end_node = 0;
		free(node);
	}

	return retval;
}

/**
* \brief Clear cache of parser
* @return true (1) - success
*/
int nmea_parser_buff_clear(nmeaPARSER *parser)
{
	NMEA_ASSERT(parser && parser->buffer);
	parser->buff_use = 0;
	return 1;
}

/**
* \brief Clear packets queue into parser
* @return true (1) - success
*/
int nmea_parser_queue_clear(nmeaPARSER *parser)
{
	NMEA_ASSERT(parser);
	while(parser->top_node)
		nmea_parser_drop(parser);
	return 1;
}
